﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using CubeDetection;
namespace CubeCalculations
{
   public class ThreadedOptimalSolver
   {
      public delegate void SetSolution(List<int> Solution, float Fitness);
      public event SetSolution OnSetSolution;
      private void InvokeOnSetSolution(List<int> SolutionList, float Fitness)
      {
         if (OnSetSolution == null)
            return;

         foreach (SetSolution Callback in OnSetSolution.GetInvocationList())
         {
            Callback(SolutionList, Fitness);
         }
      }

      public event StatusChangedHandler OnStatusChanged;

      private void InvokeOnStatusChanged(float Progress, float Fitness)
      {
         if (OnStatusChanged == null)
            return;

         foreach (StatusChangedHandler Callback in OnStatusChanged.GetInvocationList())
         {
            Callback(Progress, Fitness);
         }
      }

      readonly ThreadedEvolutionaryCubexSolutionNPlusAlpha.BacktrackingList btTemp = new ThreadedEvolutionaryCubexSolutionNPlusAlpha.BacktrackingList();

      readonly Thread[] WorkerThreads = new Thread[2];
      readonly Queue<List<int>> ThreadQueue = new Queue<List<int>>();
      private int MaxQueueLen;
      private readonly TimeStop SolutionTimeStop = new TimeStop();
      public void SolveAsync(Cubex Cube, List<int> CubeList, float Fitness)
      {
         lock (this)
         {
            ThreadsFinished = 0;
            EvaluatedSolutionList.Clear();
            btTemp.Clear();
         }
         DebugOut.Reset();


         btTemp.Add(Fitness, CubeList);
         var CurrentList = new List<int>(CubeList);
         CurrentList.RemoveAt(CurrentList.Count - 1);
         for (int i = 0; i < CubeList.Count - 1; i++)
         {
            CurrentList.RemoveAt(CurrentList.Count - 1);
            ThreadQueue.Enqueue(new List<int>(CurrentList));
         }
         MaxQueueLen = ThreadQueue.Count;

         for (int i = 0; i < WorkerThreads.Length; i++)
         {
            WorkerThreads[i] = new Thread(WorkerThreadFunction);
            WorkerThreads[i].Start(Cube);
         }
      }

      public void Interrupt()
      {
         lock (this)
         {
            ThreadQueue.Clear();
         }
      }

      public void SolveAsync(Cubex Cube, ThreadedEvolutionaryCubexSolutionNPlusAlpha.BacktrackingList Solutions)
      {
         lock (this)
         {
            SolutionTimeStop.Reset();
            ThreadsFinished = 0;
            EvaluatedSolutionList.Clear();
            btTemp.Clear();
         }
         DebugOut.Reset();

         for (int i = 0; i < 4; i++)
         {
            if (i >= Solutions.Count)
               break;

            var CurrentCompleteSolution = new List<int>(Solutions.AtIndex(i).Value);
            var WorkerCube = new Cubex(Cube);

            WorkerCube.Evaluate(Solutions.AtIndex(i).Value.ToArray());
            WorkerCube.SolveCube();
            CurrentCompleteSolution.AddRange(WorkerCube.DelegateSolution());

            btTemp.Add(Solutions.AtIndex(i).Key,CurrentCompleteSolution);



         }



         int MaxSolLen = 0;
         int MinSolLen = 100;
         foreach (var pair in btTemp)
         {
            MaxSolLen = Math.Max(MaxSolLen, pair.Value.Count/3*2);
            MinSolLen = Math.Min(MinSolLen, pair.Value.Count/4);
         }

         for (int i = 1; i < MaxSolLen; i++)
         {
            foreach (var keyValuePair in btTemp)
            {
               if(keyValuePair.Value.Count < i)
                  continue;
               var CurrentList = new List<int>(keyValuePair.Value);
               CurrentList.RemoveRange(CurrentList.Count-i,i);

               ThreadQueue.Enqueue(CurrentList);
           }
         }





         MaxQueueLen = ThreadQueue.Count;

         for (int i = 0; i < WorkerThreads.Length; i++)
         {
            WorkerThreads[i] = new Thread(WorkerThreadFunction);
            WorkerThreads[i].Start(Cube);
         }
      }

      private int ThreadsFinished;
      void ThreadFinished()
      {
         lock (this)
         {
            if (++ThreadsFinished >= 2)
            {
               Trace.WriteLine("Steps: " + btTemp.AtIndex(0).Value + " - Fitness: " + btTemp.AtIndex(0).Key);
               InvokeOnSetSolution(btTemp.AtIndex(0).Value, btTemp.AtIndex(0).Key);
            }
         }
      }

      bool DequeueThreadSafe(out List<int> SolutionList)
      {
         lock (ThreadQueue)
         {
            if (ThreadQueue.Count <= 0)
            {
               SolutionList = null;
               return false;
            }
            SolutionList = ThreadQueue.Dequeue();
            return true;
         }
      }

      readonly SortedList<int, List<int>> EvaluatedSolutionList = new SortedList<int, List<int>>();
      void AddSolution(ICollection<int> BaseList, List<int> OptSolution, float Fitness)
      {
         lock (this)
         {
            //if (!EvaluatedSolutionList.ContainsKey(BaseList.Count))
            EvaluatedSolutionList.Add(/*BaseList.Count*/EvaluatedSolutionList.Count, new List<int>(BaseList));

            OptSolution.InsertRange(0, BaseList);

            if(btTemp.Count == 0 || Fitness < btTemp.AtIndex(0).Key)
            {
               Trace.WriteLine("Better Solution found:" + SolutionTimeStop.TimeSpan() +" - Fitness: "+Fitness+" - BaseSteps: "+BaseList.Count);
            }

            if (!btTemp.ContainsKey(Fitness))
               btTemp.Add(Fitness, new List<int>(OptSolution));

            InvokeOnStatusChanged(100f * EvaluatedSolutionList.Count / MaxQueueLen,btTemp.AtIndex(0).Key);

         }
      }
      void WorkerThreadFunction(object cubex)
      {
         var ThreadedOptimalCubeSolver = new OptimalCubeSolver();
         var ThreadedMovementRating = new MovementRating();

         List<int> CurrentList;

         List<int> solutionListOut;

         var Cube = cubex as Cubex;
         var PreparedCube = new Cubex();

         while (DequeueThreadSafe(out CurrentList))
         {
            string BaseStr = PrepareCube(Cube, PreparedCube, CurrentList);
            List<CubeMoves> MoveSolution;

            if (ThreadedOptimalCubeSolver.Solve(PreparedCube.GetCubeString(), 15, out solutionListOut))
            {
               string AddStr = PreparedCube.Evaluate(solutionListOut.ToArray());
               MoveSolution = Cubex.Solution(BaseStr + AddStr);
            }
            else
            {
               PreparedCube.SolveCube();
               MoveSolution = Cubex.Solution(BaseStr + PreparedCube.solution);

            }
            float Rating = ThreadedMovementRating.RateSolution(MoveSolution);

            AddSolution(CurrentList, solutionListOut, Rating);
         }
         ThreadFinished();
      }
      private static string PrepareCube(Cubex BaseCube, Cubex PreparedCube, List<int> PreSolution)
      {
         BaseCube.CopyTo(PreparedCube);
         return PreparedCube.Evaluate(PreSolution.ToArray());
      }

   }
}
